/* 
 * Evo
 * 
 * Marcin Krupowicz (marcin@krupowicz.eu)
 * Barbara Szymanek (basia.szymanek@gmail.com)
 * 
 * January, 2009
 *
 */

using System;
using System.Collections.Generic;
using System.Text;
using Evo.Operators;

namespace Evo
{
	/// <summary>
	/// Evolutionary algorithm based on autoadaptive idea
	/// </summary>
	/// <typeparam name="T">Type of individual</typeparam>
    public class AdaptiveGeneticAlgorithm<T>: EvolutionaryAlgorithm<T>, IAlgorithm where T: IIndividual
    {
        private List<IOperatorWithContribution<T>> crossovers = new List<IOperatorWithContribution<T>>();
        private List<IOperatorWithContribution<T>> mutations = new List<IOperatorWithContribution<T>>();
        private double sigma;
        private double sumOfCrossoverContributions;
        private double sumOfMutationContributions;
        private double mutationRange;

        public double BasicRatio
        {
            get { return sigma; }
            set { sigma = value; }
        }

        public double MutationRange
        {
            get
            {
                return mutationRange;
            }
            set { mutationRange = value; }

        }

		/// <summary>
		/// Create evolutionary algorithm
		/// </summary>
		/// <param name="populationSize">size of population to be evolved</param>
		/// <param name="chromosomeLength">length of chromosome of each individual</param>
		/// <param name="maxIteration">number of iterations to be performed</param>
		/// <param name="verbose">verbose mode</param>
        public AdaptiveGeneticAlgorithm(int populationSize, int chromosomeLength, int maxIteration, bool verbose)
        :base(populationSize, chromosomeLength, maxIteration, verbose)
        {
            sigma = 0.04;
        }

        protected override void OutputStatistic(int iteration)
        {
            string ratios = "";

            foreach(IOperatorWithContribution<T> op in crossovers)
            {
                ratios += String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat,
                    " {0}, ", op.Ratio);
            }


            foreach (IOperatorWithContribution<T> op in mutations)
            {
                ratios += String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat,
                    " {0}, ", op.Ratio);
            }


			String thisIterationStatistic = String.Format(
				System.Globalization.CultureInfo.InvariantCulture.NumberFormat,
				"{0}, {1}, {2}, {3}, {4}, {5}, {6}",
				iteration, Math.Abs(population.Best.Value), Math.Abs(population.Worst.Value),
                Math.Abs(population.Average), population.StandardDeviation, 
                ratios,
                population.Best);

			verboseOutput.WriteLine(thisIterationStatistic);
        }

		/// <summary>
		/// Add new evolutionary operator
		/// </summary>
		/// <param name="newOperator">operator</param>
        public override void AddOperator(IOperator<T> newOperator)
        {
            if (newOperator as IOperatorWithContribution<T> == null)
                throw new Exception("Operators for adaptive GA must be IOperatorWithContribution");
            base.AddOperator(newOperator);

            if (newOperator as ContributionCrossoverOperator<T> != null)
                crossovers.Add(newOperator as IOperatorWithContribution<T>);
            else if (newOperator as ContributionMutationOperator<T> != null)
                mutations.Add(newOperator as IOperatorWithContribution<T>);
        }

        protected override void doIteration()
        {
            //najpierw crossover
            List<T> children = new List<T>();
            Population<T> parents;

            sumOfCrossoverContributions = 0;
            sumOfMutationContributions = 0;

            foreach (IOperatorWithContribution<T> op in crossovers)
            {
                int howMuch = (int)(population.Size * op.Ratio);
                if (howMuch >= 2)
                {
                    parents = Selection<T>.KBestSelection(population, howMuch);
                    children.AddRange(op.Execute(parents).Individuals);
                }

                sumOfCrossoverContributions += op.Contribution;
            }
            
            children.AddRange( population.Individuals);
            population = Selection<T>.KBestSelection(new Population<T>(children), population.Size);
            
            //pozniej mutation
            foreach (IOperatorWithContribution<T> op in mutations)
            {
                population = op.Execute(population);

                sumOfMutationContributions += op.Contribution;
            }
        }

        private void setRatio()
        {
            double scalef = 1.0 - (double)crossovers.Count * sigma;

            if (sumOfCrossoverContributions == 0)
            {
                foreach (IOperatorWithContribution<T> op in crossovers)
                    op.Ratio = 1.0 / (double)crossovers.Count;
            }
            else
            {
                foreach (IOperatorWithContribution<T> op in crossovers)
                    op.Ratio = scalef * op.Contribution / sumOfCrossoverContributions + sigma;
            }

            scalef = 1.0 - (double)mutations.Count * sigma;

            if (sumOfMutationContributions == 0)
            {
                foreach (IOperatorWithContribution<T> op in mutations)
                    op.Ratio = 1.0 / (double)mutations.Count;
            }
            else
            {
                foreach (IOperatorWithContribution<T> op in mutations)
                    op.Ratio = scalef * op.Contribution * mutationRange/ sumOfMutationContributions + sigma;

            }
        }

        private void initializeRatio()
        {
            foreach (IOperatorWithContribution<T> op in crossovers)
                op.Ratio = 1.0 / (double)crossovers.Count;

            foreach (IOperatorWithContribution<T> op in mutations)
                op.Ratio = 1.0*mutationRange/ (double)mutations.Count;
        }

		/// <summary>
		/// Run algorithm
		/// </summary>
        public override void RunAlgorithm()
        {
            if (objectiveFunction == null)
                throw new Exception("Objective function is not assign!");

            if (operators.Count == 0)
                throw new Exception("There is no operators to execute!");

            if (population == null)
                population = Population<T>.GenerateRandom(populationSize, chromosomeLength);

            population.ObjectiveFunction = this.objectiveFunction;

            int iterationCounter = 0;

            population.Evaluate(fitnessScaling);
            initializeRatio();

            while (iterationCounter < maxIteration)
            {
                doIteration();
                setRatio();

                if (verboseOutput != null && verbose)
                    OutputStatistic(iterationCounter);

                iterationCounter++;
            }
        }
    }
}
